// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

#include <QvisText3DInterface.h>
#include <QButtonGroup>
#include <QCheckBox>
#include <QComboBox>
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QRadioButton>
#include <QSpinBox>
#include <QvisColorButton.h>
#include <QvisOpacitySlider.h>
#include <QvisScreenPositionEdit.h>

#include <AnnotationObject.h>

#define GetFacesCamera GetOptions().GetEntry("preserveOrientation")->AsBool
#define SetFacesCamera GetOptions().GetEntry("preserveOrientation")->SetValue

#define GetRotations GetPosition2
#define SetRotations SetPosition2

// The flag that lets us switch between relative and fixed heights.
#define GetRelativeHeightMode GetOptions().GetEntry("useRelativeHeight")->AsBool
#define SetRelativeHeightMode GetOptions().GetEntry("useRelativeHeight")->SetValue

// Relative scale will scale the text as a percentage of the bbox diagonal
#define GetRelativeHeight GetOptions().GetEntry("relativeHeight")->AsInt
#define SetRelativeHeight GetOptions().GetEntry("relatveHeight")->SetValue

// Fixed height in world coordinates.
#define GetFixedHeight   GetOptions().GetEntry("fixedHeight")->AsDouble
#define SetFixedHeight   GetOptions().GetEntry("fixedHeight")->SetValue

// ****************************************************************************
// Method: QvisText3DInterface::QvisText3DInterface
//
// Purpose:
//   Constructor for the QvisText3DInterface class.
//
// Arguments:
//   parent : This widget's parent widget.
//   name   : The name of this widget.
//
// Programmer: Brad Whitlock
// Creation:   Wed Nov 7 11:46:42 PDT 2007
//
// Modifications:
//   Brad Whitlock, Mon Jul 21 11:33:44 PDT 2008
//   Qt 4.
//
//   Kathleen Biagas, Mon Jul 13 13:11:00 PDT 2015
//   Place 'useForeground' checkbox before text color button, added
//   textColorLabel so it can be disabled when the button is disabled.
//
//   Kathleen Biagas, Tue Apr 18 16:34:41 PDT 2023
//   Support Qt6: buttonClicked -> idClicked.
//
// ****************************************************************************

QvisText3DInterface::QvisText3DInterface(QWidget *parent) :
    QvisAnnotationObjectInterface(parent)
{
    // Set the title of the group box.
    this->setTitle(GetName());

    QGridLayout *cLayout = new QGridLayout(0);
    topLayout->addLayout(cLayout);
    cLayout->setSpacing(10);

    int row = 0;

    // Add controls for entering the text
    textLineEdit = new QLineEdit(this);
    connect(textLineEdit, SIGNAL(returnPressed()),
            this, SLOT(textChanged()));
    cLayout->addWidget(textLineEdit, row, 1, 1, 2);
    cLayout->addWidget(new QLabel(tr("Text"), this), row, 0);
    ++row;

    // Add controls for the position
    positionEdit = new QLineEdit(this);
    connect(positionEdit, SIGNAL(returnPressed()),
            this, SLOT(positionChanged()));
    cLayout->addWidget(positionEdit, row, 1, 1, 2);
    cLayout->addWidget(new QLabel(tr("Position"), this), row, 0);
    ++row;

    // Add controls for the height.
    heightMode = new QButtonGroup(this);
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
    connect(heightMode, SIGNAL(buttonClicked(int)),
            this, SLOT(heightModeChanged(int)));
#else
    connect(heightMode, SIGNAL(idClicked(int)),
            this, SLOT(heightModeChanged(int)));
#endif
    cLayout->addWidget(new QLabel(tr("Height"), this), row, 0);
    QRadioButton *rb = new QRadioButton(tr("Relative"), this);
    heightMode->addButton(rb, 0);
    cLayout->addWidget(rb, row, 1);
    // Add controls for relative height
    relativeHeightSpinBox = new QSpinBox(this);
    relativeHeightSpinBox->setKeyboardTracking(false);
    relativeHeightSpinBox->setMinimum(1);
    relativeHeightSpinBox->setMaximum(100);
    relativeHeightSpinBox->setSuffix("%");
    relativeHeightSpinBox->setButtonSymbols(QSpinBox::PlusMinus);
    connect(relativeHeightSpinBox, SIGNAL(valueChanged(int)),
            this, SLOT(relativeHeightChanged(int)));
    cLayout->addWidget(relativeHeightSpinBox, row, 2);
    ++row;

    // Add controls for fixed height.
    rb = new QRadioButton(tr("Fixed"), this);
    heightMode->addButton(rb, 1);
    cLayout->addWidget(rb, row, 1);
    fixedHeightEdit = new QLineEdit(this);
    connect(fixedHeightEdit, SIGNAL(returnPressed()),
            this, SLOT(fixedHeightChanged()));
    cLayout->addWidget(fixedHeightEdit, row, 2);
    cLayout->setColumnStretch(2, 10);
    ++row;

    QFrame *splitter1 = new QFrame(this);
    splitter1->setFrameStyle(QFrame::HLine + QFrame::Raised);
    cLayout->addWidget(splitter1, row, 0, 1, 3);
    ++row;

    facesCameraCheckBox = new QCheckBox(tr("Preserve orientation when view changes"), this);
    connect(facesCameraCheckBox, SIGNAL(toggled(bool)),
            this, SLOT(facesCameraToggled(bool)));
    cLayout->addWidget(facesCameraCheckBox, row, 0, 1, 3);
    ++row;

    rotateZ = new QSpinBox(this);
    rotateZ->setKeyboardTracking(false);
    rotateZ->setMinimum(-360);
    rotateZ->setMaximum(360);
    rotateZ->setButtonSymbols(QSpinBox::PlusMinus);
    rotateZ->setSuffix(" deg");
    connect(rotateZ, SIGNAL(valueChanged(int)),
            this, SLOT(rotateZChanged(int)));
    QLabel *rotateZLabel = new QLabel(tr("Rotate Z"), this);
    rotateX = new QSpinBox(this);
    rotateX->setKeyboardTracking(false);
    rotateX->setMinimum(-360);
    rotateX->setMaximum(360);
    rotateX->setButtonSymbols(QSpinBox::PlusMinus);
    rotateX->setSuffix(" deg");
    connect(rotateX, SIGNAL(valueChanged(int)),
            this, SLOT(rotateXChanged(int)));
    QLabel *rotateXLabel = new QLabel(tr("Rotate X"), this);
    rotateY = new QSpinBox(this);
    rotateY->setKeyboardTracking(false);
    rotateY->setMinimum(-360);
    rotateY->setMaximum(360);
    rotateY->setButtonSymbols(QSpinBox::PlusMinus);
    rotateY->setSuffix(" deg");
    connect(rotateY, SIGNAL(valueChanged(int)),
            this, SLOT(rotateYChanged(int)));
    QLabel *rotateYLabel = new QLabel(tr("Rotate Y"), this);
    QGridLayout *rLayout = new QGridLayout(0);
    rLayout->setContentsMargins(0,0,0,0);
    cLayout->addLayout(rLayout, row, 0, 1, 3);
    rLayout->addWidget(rotateYLabel, 0, 0);
    rLayout->addWidget(rotateXLabel, 0, 1);
    rLayout->addWidget(rotateZLabel, 0, 2);
    rLayout->addWidget(rotateY, 1, 0);
    rLayout->addWidget(rotateX, 1, 1);
    rLayout->addWidget(rotateZ, 1, 2);
    ++row;

    QFrame *splitter2 = new QFrame(this);
    splitter2->setFrameStyle(QFrame::HLine + QFrame::Raised);
    cLayout->addWidget(splitter2, row, 0, 1, 4);
    ++row;

    // Added a use foreground toggle
    useForegroundColorCheckBox = new QCheckBox(tr("Use foreground color"), this);
    connect(useForegroundColorCheckBox, SIGNAL(toggled(bool)),
            this, SLOT(useForegroundColorToggled(bool)));
    cLayout->addWidget(useForegroundColorCheckBox, row, 0, 1, 3);
    ++row;

    // Add controls for the text color.
    textColorLabel = new QLabel(tr("Text color"), this);
    cLayout->addWidget(textColorLabel, row, 0, Qt::AlignLeft);

    textColorButton = new QvisColorButton(this);
    connect(textColorButton, SIGNAL(selectedColor(const QColor &)),
            this, SLOT(textColorChanged(const QColor &)));
    cLayout->addWidget(textColorButton, row, 1);
    textColorOpacity = new QvisOpacitySlider(0, 255, 10, 0, this);
    connect(textColorOpacity, SIGNAL(valueChanged(int)),
            this, SLOT(textOpacityChanged(int)));
    cLayout->addWidget(textColorOpacity, row, 2, 1, 2);
    ++row;


    // Added a visibility toggle
    visibleCheckBox = new QCheckBox(tr("Visible"), this);
    connect(visibleCheckBox, SIGNAL(toggled(bool)),
            this, SLOT(visibilityToggled(bool)));
    cLayout->addWidget(visibleCheckBox, row, 0);
}

// ****************************************************************************
// Method: QvisText3DInterface::~QvisText3DInterface
//
// Purpose:
//   Destructor for the QvisText3DInterface class.
//
// Programmer: Brad Whitlock
// Creation:   Wed Nov 7 11:47:58 PDT 2007
//
// Modifications:
//
// ****************************************************************************

QvisText3DInterface::~QvisText3DInterface()
{
}

// ****************************************************************************
// Method: QvisText3DInterface::GetMenuText
//
// Purpose:
//   Returns the text to use in the annotation list box.
//
// Arguments:
//   annot : The annotation object to use for extra information.
//
// Returns:    The text to use in the annotation list box.
//
// Programmer: Brad Whitlock
// Creation:   Wed Nov 7 16:06:47 PST 2007
//
// Modifications:
//   Brad Whitlock, Mon Jul 21 11:37:41 PDT 2008
//   Qt 4.
//
// ****************************************************************************

QString
QvisText3DInterface::GetMenuText(const AnnotationObject &annot) const
{
    QString retval;
    if(annot.GetText().size() > 0)
        retval = QString("%1 - %2").arg(GetName()).arg(annot.GetText()[0].c_str());
    else
        retval = GetName();

    return retval;
}

// ****************************************************************************
// Method: QvisText3DInterface::UpdateControls
//
// Purpose:
//   Updates the controls in the interface using the data in the Annotation
//   object pointed to by the annot pointer.
//
// Programmer: Brad Whitlock
// Creation:   Wed Nov 7 11:48:15 PDT 2007
//
// Modifications:
//   Brad Whitlock, Mon Jul 21 11:38:06 PDT 2008
//   Qt 4.
//
//   Kathleen Biagas, Mon Jul 13 13:19:45 PDT 2015
//   Enable/disable textColorLabel along with textColorButton.
//
//   Kathleen Biagas, Thu Jan 21, 2021
//   Replace QString.asprintf with QString.arg and QString.setNum.
//
// ****************************************************************************

void
QvisText3DInterface::UpdateControls()
{
    // Set the text
    const stringVector &annotText = annot->GetText();
    if(annotText.size() > 0)
        textLineEdit->setText(annotText[0].c_str());
    else
        textLineEdit->setText("");

    // Set the position.
    QString pos = QString("%1 %2 %3")
        .arg(annot->GetPosition()[0])
        .arg(annot->GetPosition()[1])
        .arg(annot->GetPosition()[2]);
    positionEdit->setText(pos);

    // Set the height mode
    heightMode->blockSignals(true);
    heightMode->button(annot->GetRelativeHeightMode()?0:1)->setChecked(true);
    heightMode->blockSignals(false);
    relativeHeightSpinBox->setEnabled(annot->GetRelativeHeightMode());
    fixedHeightEdit->setEnabled(!annot->GetRelativeHeightMode());

    // Set the spinbox value for relative height.
    relativeHeightSpinBox->blockSignals(true);
    relativeHeightSpinBox->setValue(annot->GetRelativeHeight());
    relativeHeightSpinBox->blockSignals(false);

    // Set the value for the fixed height.
    QString tmp;
    tmp.setNum(annot->GetFixedHeight());
    fixedHeightEdit->setText(tmp);

    // Set the faces camera check box.
    facesCameraCheckBox->blockSignals(true);
    facesCameraCheckBox->setChecked(annot->GetFacesCamera());
    facesCameraCheckBox->blockSignals(false);

    // Set the rotation values.
    int rx = (int)annot->GetRotations()[0];
    int ry = (int)annot->GetRotations()[1];
    int rz = (int)annot->GetRotations()[2];
    rotateX->blockSignals(true); rotateX->setValue(rx); rotateX->blockSignals(false);
    rotateY->blockSignals(true); rotateY->setValue(ry); rotateY->blockSignals(false);
    rotateZ->blockSignals(true); rotateZ->setValue(rz); rotateZ->blockSignals(false);

    //
    // Set the text color. If we're using the foreground color for the text
    // color then make the button be white and only let the user change the
    // opacity.
    //
    textColorOpacity->blockSignals(true);
    if(annot->GetUseForegroundForTextColor())
    {
        QColor tmp(255,255,255);
        textColorButton->setButtonColor(tmp);
        textColorButton->setEnabled(false);
        textColorLabel->setEnabled(false);
        textColorOpacity->setGradientColor(tmp);
    }
    else
    {
        QColor tc(annot->GetTextColor().Red(),
                  annot->GetTextColor().Green(),
                  annot->GetTextColor().Blue());
        textColorButton->setButtonColor(tc);
        textColorButton->setEnabled(true);
        textColorLabel->setEnabled(true);
        textColorOpacity->setGradientColor(tc);
    }
    textColorOpacity->setValue(annot->GetTextColor().Alpha());
    textColorOpacity->blockSignals(false);

    // Set the use foreground color check box.
    useForegroundColorCheckBox->blockSignals(true);
    useForegroundColorCheckBox->setChecked(annot->GetUseForegroundForTextColor());
    useForegroundColorCheckBox->blockSignals(false);

    // Set the visible check box.
    visibleCheckBox->blockSignals(true);
    visibleCheckBox->setChecked(annot->GetVisible());
    visibleCheckBox->blockSignals(false);
}

// ****************************************************************************
// Method: QvisText3DInterface::GetCurrentValues
//
// Purpose:
//   Gets the current values for the text fields.
//
// Arguments:
//   which_widget : The widget for which we're getting the values. -1 for all.
//
// Programmer: Brad Whitlock
// Creation:   Wed Nov 7 11:49:08 PDT 2007
//
// Modifications:
//   Brad Whitlock, Tue Apr  8 16:29:55 PDT 2008
//   Support for internationalization.
//
//   Brad Whitlock, Mon Jul 21 11:41:26 PDT 2008
//   Qt 4.
//
// ****************************************************************************

void
QvisText3DInterface::GetCurrentValues(int which_widget)
{
    bool doAll = (which_widget == -1);

    if(which_widget == 0 || doAll)
    {
        stringVector sv;
        sv.push_back(textLineEdit->text().toStdString());
        annot->SetText(sv);
    }

    if(which_widget == 1 || doAll)
    {
        double v[3];
        if(LineEditGetDoubles(positionEdit, v, 3))
            annot->SetPosition(v);
        else
        {
            QString msg = tr("The position must be specified as a 3D coordinate. "
                             "Resetting to the last good value of %1.").
                arg(DoublesToQString(annot->GetPosition(), 3));
            Error(msg);
            annot->SetPosition(annot->GetPosition());
        }
    }

    if(which_widget == 2 || doAll)
    {
        // Get its new current value and store it in the atts.
        annot->SetRelativeHeight(relativeHeightSpinBox->value());
    }

    if(which_widget == 3 || doAll)
    {
        double v;
        bool okay = false;
        if(LineEditGetDouble(fixedHeightEdit, v))
        {
            okay = v > 0.;
            if(okay)
                annot->SetFixedHeight(v);
        }

        if (!okay)
        {
            QString msg = tr("The fixed height is a floating point number greater than zero. "
                             "Resetting to the last good value of %1.").
                          arg(annot->GetFixedHeight());
            Error(msg);
            annot->SetFixedHeight(annot->GetFixedHeight());
        }
    }

    if(which_widget == 4 || doAll)
    {
        // Get its new current value and store it in the atts.
        int r = rotateZ->value();
        double rotations[3];
        rotations[0] = annot->GetRotations()[0];
        rotations[1] = annot->GetRotations()[1];
        rotations[2] = (double)r;
        annot->SetRotations(rotations);
    }

    if(which_widget == 5 || doAll)
    {
        // Get its new current value and store it in the atts.
        int r = rotateX->value();
        double rotations[3];
        rotations[0] = (double)r;
        rotations[1] = annot->GetRotations()[1];
        rotations[2] = annot->GetRotations()[2];
        annot->SetRotations(rotations);
    }

    if(which_widget == 6 || doAll)
    {
        // Get its new current value and store it in the atts.
        int r = rotateY->value();
        double rotations[3];
        rotations[0] = annot->GetRotations()[0];
        rotations[1] = (double)r;
        rotations[2] = annot->GetRotations()[2];
        annot->SetRotations(rotations);
    }
}

//
// Qt Slot functions
//

// ****************************************************************************
// Method: QvisText3DInterface::textChanged
//
// Purpose:
//   This is a Qt slot function that is called when return is pressed in the
//   text line edit.
//
// Programmer: Brad Whitlock
// Creation:   Wed Nov 7 11:49:46 PDT 2007
//
// Modifications:
//
// ****************************************************************************

void
QvisText3DInterface::textChanged()
{
    GetCurrentValues(0);
    Apply();
}

// ****************************************************************************
// Method: QvisText3DInterface::positionChanged
//
// Purpose:
//   This is a Qt slot function that is called when return is pressed in the
//   position line edit.
//
// Programmer: Brad Whitlock
// Creation:   Wed Nov 7 11:49:46 PDT 2007
//
// Modifications:
//
// ****************************************************************************

void
QvisText3DInterface::positionChanged()
{
    GetCurrentValues(1);
    Apply();
}

void
QvisText3DInterface::heightModeChanged(int mode)
{
    bool doRelative = mode == 0;
    annot->SetRelativeHeightMode(doRelative);
    Apply();
}

void
QvisText3DInterface::relativeHeightChanged(int h)
{
    annot->SetRelativeHeight(h);
    SetUpdate(false);
    Apply();
}

void
QvisText3DInterface::fixedHeightChanged()
{
    GetCurrentValues(3);
    Apply();
}

// ****************************************************************************
// Method: QvisText3DInterface::facesCameraToggled
//
// Purpose:
//   This is a Qt slot function that is called when the faces camera checkbox
//   is toggled.
//
// Arguments:
//   val : The new bold flag.
//
// Programmer: Brad Whitlock
// Creation:   Thu Nov 8 16:01:03 PST 2007
//
// Modifications:
//
// ****************************************************************************

void
QvisText3DInterface::facesCameraToggled(bool val)
{
    annot->SetFacesCamera(val);
    SetUpdate(false);
    Apply();
}

void
QvisText3DInterface::rotateZChanged(int)
{
    GetCurrentValues(4);
    Apply();
}

void
QvisText3DInterface::rotateXChanged(int)
{
    GetCurrentValues(5);
    Apply();
}

void
QvisText3DInterface::rotateYChanged(int)
{
    GetCurrentValues(6);
    Apply();
}

// ****************************************************************************
// Method: QvisText3DInterface::textColorChanged
//
// Purpose:
//   This is a Qt slot function that is called when a new start color is
//   selected.
//
// Arguments:
//   c : The new start color.
//
// Programmer: Brad Whitlock
// Creation:   Wed Nov 7 11:49:46 PDT 2007
//
// Modifications:
//
// ****************************************************************************

void
QvisText3DInterface::textColorChanged(const QColor &c)
{
    int a = annot->GetTextColor().Alpha();
    ColorAttribute tc(c.red(), c.green(), c.blue(), a);
    annot->SetTextColor(tc);
    Apply();
}

// ****************************************************************************
// Method: QvisText3DInterface::textOpacityChanged
//
// Purpose:
//   This is a Qt slot function that is called when a new start opacity is
//   selected.
//
// Arguments:
//   opacity : The new start opacity.
//
// Programmer: Brad Whitlock
// Creation:   Wed Nov 7 11:49:46 PDT 2007
//
// Modifications:
//
// ****************************************************************************

void
QvisText3DInterface::textOpacityChanged(int opacity)
{
    ColorAttribute tc(annot->GetTextColor());
    tc.SetAlpha(opacity);
    annot->SetTextColor(tc);
    SetUpdate(false);
    Apply();
}

// ****************************************************************************
// Method: QvisText3DInterface::useForegroundColorToggled
//
// Purpose:
//   This is a Qt slot function that is called when the useForegroundColor
//   check box is clicked.
//
// Arguments:
//   val : The new setting for useForegroundColor
//
// Programmer: Brad Whitlock
// Creation:   Wed Nov 7 12:34:48 PDT 2007
//
// Modifications:
//
// ****************************************************************************

void
QvisText3DInterface::useForegroundColorToggled(bool val)
{
    annot->SetUseForegroundForTextColor(val);
    Apply();
}

// ****************************************************************************
// Method: QvisText3DInterface::visibilityToggled
//
// Purpose:
//   This is a Qt slot function that is called when the visibility toggle is
//   changed.
//
// Arguments:
//   val : The visibility flag.
//
// Programmer: Brad Whitlock
// Creation:   Wed Nov 7 11:49:46 PDT 2007
//
// Modifications:
//
// ****************************************************************************

void
QvisText3DInterface::visibilityToggled(bool val)
{
    annot->SetVisible(val);
    SetUpdate(false);
    Apply();
}
